package business;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

import util.ComponentAspectNames;
import util.Globals;
import util.IntertypeType;
import exception.AdviceHasntPointcutAssociatedOnAspectException;
import exception.FunctionDeclarationIntertypeAlreadyExistsOnAspectException;
import exception.VarDeclarationIntertypeAlreadyExistsOnAspectException;

public class AspectComponents {
	private List<Advice> adviceList;
	private List<Pointcut> pointcutList;
	private List<Intertype> intertypeList;
	private List<AdvicePointcut> advicePointcutList;

	/* Every aspectComponents needs access these fields to verify if it is duplicated. */
	static List<String> varDeclarationList = new ArrayList<String>();
	static List<String> functionDeclarationList = new ArrayList<String>();

	public AspectComponents(List<Advice> adviceList,
			List<Pointcut> pointcutList, List<Intertype> intertypeList,
			List<AdvicePointcut> advicePointcutList) {
		this.adviceList = adviceList;
		this.pointcutList = pointcutList;
		this.intertypeList = intertypeList;
		this.advicePointcutList = advicePointcutList;
	}

	/**
	 * Make a semantic analysis on the aspect.
	 * 
	 * @param aspectIdentifier
	 * @throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws VarDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws AdviceHasntPointcutAssociatedOnAspectException
	 */
	public void makeSemanticAnalysisOnAspectComponents(String aspectIdentifier) throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException, AdviceHasntPointcutAssociatedOnAspectException{
		checkIntertypes();
		checkAdvicesAndPointcuts(aspectIdentifier);
	}

	/**
	 * Checks if there is intertypes on aspect:
	 * -Adding the same function, var, etc.
	 * 
	 * @param aspectIdentifier
	 * @throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException
	 * @throws VarDeclarationIntertypeAlreadyExistsOnAspectException
	 */
	private void checkIntertypes() throws FunctionDeclarationIntertypeAlreadyExistsOnAspectException, VarDeclarationIntertypeAlreadyExistsOnAspectException{
		if(intertypeList != null && !intertypeList.isEmpty()){
			for(Intertype intertype : intertypeList){

				/* Check the intertype type: */
				if(intertype.getType() == IntertypeType.FUNCTION){
					String functionId = Globals.retriveIdFromBodyFunction(intertype.getCodeToInclude());

					/* If this id doesnt exists, puts on the list. */
					if(!Globals.verifyStringAlreadyExistsInList(functionDeclarationList, functionId)){
						functionDeclarationList.add(functionId);
					}else{ /* Else, throws a exception. */
						throw new FunctionDeclarationIntertypeAlreadyExistsOnAspectException(functionId);
					}
				}else if (intertype.getType() == IntertypeType.VAR || intertype.getType() == IntertypeType.GLOBAL){
					/* Gets var id from intertype. */
					String varId = Globals.extractIdFromCodeToInclude(intertype);

					/* If this id doesnt exists, puts on the list. */
					if(!Globals.verifyStringAlreadyExistsInList(varDeclarationList, varId)){
						varDeclarationList.add(varId);
					}else{ /* Else, throws a exception. */
						throw new VarDeclarationIntertypeAlreadyExistsOnAspectException(varId);
					}
				}
			}
		}
	}

	/**
	 * Pointcut refering a part of netlogo code to include something or modify the
	 * original action. It will only happen if exists a advice associating to this 
	 * pointcut, because they say the time of the action.
	 * 
	 * This method checks:
	 * -If all advices declared on aspect have a pointcuts associated.
	 * 
	 * If exists a pointcut with no advice associated, it will not be a compilation error,
	 * but it wont affect netlogo code.
	 * 
	 * @param aspectIdentifier
	 * @throws AdviceHasntPointcutAssociatedOnAspectException
	 */
	private void checkAdvicesAndPointcuts(String aspectIdentifier) throws AdviceHasntPointcutAssociatedOnAspectException{

		/* Verifies if exists advice on list. */
		boolean existsAdviceOnList = (adviceList != null && !pointcutList.isEmpty());

		/* Verifies if exists pointcut on list. */
		boolean existsPointcutOnList = (pointcutList != null && !pointcutList.isEmpty());

		if(existsAdviceOnList){
			if(existsPointcutOnList){
				for(Advice advice : adviceList){ 
					/* boolean with advice associated pointcut status. */ 
					boolean existsAdviceIdOnPointcutList = false;

					/* Gets pointcut refering list. */
					List<String> advicePointcutReferingList = advice.getPointcutsReferingList();

					if(advicePointcutReferingList != null && !advicePointcutReferingList.isEmpty()){
						/* For each advice pointcut refering, verifies if it has a pointcut associated. */
						for(String pointcutRefering : advicePointcutReferingList){
							/* For each pointcut in pointcut list, checks if exists 'pointcutRefering' value. */
							for(Pointcut pointcut : pointcutList){

								if(pointcut.getPointcutId().equalsIgnoreCase(pointcutRefering)){
									existsAdviceIdOnPointcutList = true;
									break;
								}
							}
							if(!existsAdviceIdOnPointcutList){ /* If there is no pointcut, throws a exception. */
								throw new AdviceHasntPointcutAssociatedOnAspectException(pointcutRefering, aspectIdentifier);
							}
							existsAdviceIdOnPointcutList = false;
						}
					}
				}
			}else{ /* If there is no pointcut list, all advice are no associated. So throws a exception. */
				throw new AdviceHasntPointcutAssociatedOnAspectException(adviceList, aspectIdentifier);
			}
		}
	}

	public List<Advice> getAdviceList() {
		return adviceList;
	}

	public void setAdviceList(List<Advice> adviceList) {
		this.adviceList = adviceList;
	}

	public List<Pointcut> getPointcutList() {
		return pointcutList;
	}

	public void setPointcutList(List<Pointcut> pointcutList) {
		this.pointcutList = pointcutList;
	}

	public List<Intertype> getIntertypeList() {
		return intertypeList;
	}

	public void setIntertypeList(List<Intertype> intertypeList) {
		this.intertypeList = intertypeList;
	}

	public List<AdvicePointcut> getAdvicePointcutList() {
		return advicePointcutList;
	}

	public void setAdvicePointcutList(List<AdvicePointcut> advicePointcutList) {
		this.advicePointcutList = advicePointcutList;
	}

	/**
	 * Returns a resume contents all aspect components informations.
	 */
	public String toString(){
		String printResume;
		if((adviceList == null || adviceList.isEmpty()) &&
				(pointcutList == null || pointcutList.isEmpty()) &&
				(intertypeList == null || intertypeList.isEmpty()) &&
				(advicePointcutList == null || advicePointcutList.isEmpty())){
			printResume = "";
		}else{
			printResume = "\nThese aspect components don't interfered on netlogo code: ";
			if(pointcutList != null && !pointcutList.isEmpty()){
				printResume = printResume + Globals.BREAK_LINE + ComponentAspectNames.POINTCUT.toString() + " - " + pointcutList;
			}
			if(advicePointcutList != null && !advicePointcutList.isEmpty()){
				printResume = printResume + Globals.BREAK_LINE + ComponentAspectNames.ADVICE_POINTCUT_SAME_LINE.toString() + " - " + advicePointcutList;
			}
			if(intertypeList != null && !intertypeList.isEmpty()){
				printResume = printResume + Globals.BREAK_LINE + ComponentAspectNames.INTERTYPE.toString() + " - " + intertypeList;
			}
		}

		return printResume;
	}
}
